<?php
declare(strict_types=1);



use ErrorException;
use PDO;
use PDOException;
use PDOStatement;

/**
 * Class Table
 * @package txz\common\third_lib\model\lib
 */
class AAA extends AbstractTable
{
    private $sql;

    /**
     * @param $dbName
     * @param $tableName
     * @param $pre
     * @param $suffix
     * @return mixed
     */
    public function process($dbName, $tableName, $pre = '', $suffix = ''):void
    {
        $namespace_arr = explode('\\', $dbName);
        $dbName = $namespace_arr[count($namespace_arr) - 1];

        $class_arr = explode('\\', $tableName);
        $tableName = $class_arr[count($class_arr) - 1];

        $this->setPre($pre);
        $this->setSuffix($suffix);
        $this->setDbName($dbName);
        $this->setTableName(($pre ? $pre . '_' : '') . $tableName . ($suffix ? '_' . $suffix : ''));
    }

    /**
     * @return string
     */
    public function getLastSql(): string
    {
        return $this->sql;
    }

    /**
     * @return string
     */
    public function getLastInsertId(): string
    {
        return self::getCon()->lastInsertId();
    }

    /**
     * @return int
     */
    public function getErrorCode()
    {
        return self::getCon()->errorCode();
    }

    /**
     * @return string
     */
    public function getErrMsg()
    {
        return var_export(self::getCon()->errorInfo());
    }

    /**
     * @param $string
     * @return bool|string
     */
    public function escape_string($string)
    {
        return $this->getCon()->quote($string);
    }


    private function handle_db_error()
    {
        if (Util::isService()) {
            Util::println([
                "sql:" . $this->getLastSql(),
                "code:" . $this->getCon()->errorCode(),
                $this->getCon()->errorInfo()
            ]);
        }
        if ($this->getCon()->errorCode() === 2006) { // mysql gone away
            DbMgr::del($this->getTableName());
        }
    }

    /**
     * @return PDO
     */
    function getCon(): PDO
    {
        return DbMgr::get($this);
    }


    /**
     * @param $_sql
     * @return bool
     * @throws ErrorException
     */
    public function execute($_sql)
    {
        try {
            $this->sql = $_sql;
            $_stmt = $this->getCon()->prepare($_sql);
            if(!$_stmt){
                throw new ErrorException($this->sql);
            }
            $res = $_stmt->execute();
            $_stmt->closeCursor();
            return $res;
        } catch (PDOException  $e) {
            $this->handle_db_error();
            return false;
        }
    }

    /**
     * @param $where
     * @return bool|string
     */
    private function makeWhere(&$where)
    {
        $count = 65;
        if (is_array($where) && $where) {
            $str = 'WHERE ';
            foreach ($where as $k => $v) {
                if (is_array($v)) {
                    $fuhao = $v[0];
                    $v = $v[1];
                } else {
                    $fuhao = '=';
                }
                $tmp = ':a' . chr($count++);
                $str .= "`$k`".$fuhao.$tmp." AND ";
                $where[$tmp] = $v;
                unset($where[$k]);
            }
            $str = substr($str, 0, -5);
            return $str;
        } else {
            return '';
        }
    }

    /**
     * @param $need
     * @return bool|string
     */
    private function makeNeed($need)
    {
        if (is_array($need) && $need) {
            $str = '';
            foreach ($need as $k => $v) {
                $str .= "`$v`,";
            }
            $str = substr($str, 0, -1);
            return $str;
        } else {
            return '*';
        }
    }

    /**
     * @param $group
     * @return string
     */
    public function makeGroup($group)
    {
        if ($group)
            return "GROUP BY $group";
        else
            return '';
    }

    /**
     * @param $limit
     * @return string
     */
    public function makeLimit($limit)
    {
        if ($limit && is_array($limit))
            return "LIMIT " . $limit[0] . "," . $limit[1];
        else
            return '';
    }

    /**
     * @param $data
     * @return array|string
     */
    private function makeSetData($data)
    {
        $count = 65;
        if (is_object($data)){
            $data = json_decode(json_encode($data), true);
        }
        if ($data && is_array($data)) {
            $str = 'SET ';
            foreach ($data as $k => $v) {
                $tmp = ":b" . chr($count++);
                $str .= "`{$k}`=$tmp,";
                $data[$tmp] = $v;
                unset($data[$k]);
            }
            $str = substr($str, 0, -1);
            return [$str, $data];
        } else {
            return '';
        }
    }

    /**
     * @param PDOStatement $stmt
     * @param $data
     */
    private function bindParams(PDOStatement $stmt, $data)
    {
        if (is_array($data)) {
            foreach ($data as $k => $v) {
                if (is_array($v)) {
                    foreach ($v as $key => $val) {
                        $stmt->bindValue($key, $val);
                    }
                } else {
                    $stmt->bindValue($k, $v);
                }
            }
        }
    }

    /**
     * @param string $need
     * @param string $where
     * @param string $group
     * @param string $sort
     * @return array|mixed
     * @throws ErrorException
     */
    public function getObj($need = '', $where = '', $group = '', $sort = '')
    {
        $tableName = $this->getTableName();
        try {
            if (!$tableName)
                throw new ErrorException('no table');

            $str = $this->makeWhere($where);
            if (!$str)
                throw new ErrorException('cant find');

            $need = $this->makeNeed($need);
            $group = $this->makeGroup($group);
            $sort = $this->makeOrder($sort);
            $this->sql = "SELECT $need FROM `$tableName` {$str} $group {$sort}";

            $_stmt = $this->getCon()->prepare($this->sql);
            if(!$_stmt){
                throw new ErrorException($this->sql);
            }
            if (is_array($where)) {
                $this->bindParams($_stmt, $where);
            }
            $_stmt->execute();

            $res = $_stmt->fetch(PDO::FETCH_ASSOC);
            $_stmt->closeCursor();
            if (!is_array($res) || !$res) {
                return [];
            } else {
                return $res;
            }
        } catch (PDOException  $e) {
            $this->handle_db_error();
            throw new ErrorException($e->getMessage());
        } catch (ErrorException $e) {
            throw new ErrorException($e->getMessage());
        }
    }

    /**
     * @param null $where
     * @return int
     * @throws ErrorException
     */
    public function getObjCount($where = null)
    {
        $tableName = $this->getTableName();
        try {
            if (!$tableName)
                throw new ErrorException('no table');

            $str = $this->makeWhere($where);
            $this->sql = "SELECT count(*) FROM `$tableName` $str";
            $_stmt = $this->getCon()->prepare($this->sql);
            if(!$_stmt){
                throw new ErrorException($this->sql);
            }
            $this->bindParams($_stmt, $where);
            $_stmt->execute();
            $res = $_stmt->fetch(PDO::FETCH_ASSOC);
            $_stmt->closeCursor();
            if (!is_array($res) || !$res) {
                return 0;
            } else {
                return array_values($res)[0];
            }
        } catch (PDOException  $e) {
            $this->handle_db_error();
            throw new ErrorException($e->getMessage());
        } catch (ErrorException $e) {
            throw new ErrorException($e->getMessage());
        }
    }

    /**
     * @param $sort
     * @return string
     */
    private function makeOrder($sort)
    {
        if ($sort) {
            if (is_array($sort)) {
                $sort = array_values($sort);
                return "ORDER BY `{$sort[0]}` ASC";
            } else {
                return "ORDER BY `{$sort}` DESC";
            }
        }
        return '';
    }

    /**
     * @param string $need
     * @param string $where
     * @param string $group
     * @param string $sort
     * @param string $limit
     * @param $flag
     * @return array
     * @throws ErrorException
     */
    public function getObjArr($need = '', $where = '',
                                $group = '', $sort = '',
                                $limit = '', $flag)
    {
        $tableName = $this->getTableName();
        try {
            if (!$tableName)
                throw new ErrorException('no table');

            $str = $this->makeWhere($where);
            $group = $this->makeGroup($group);
            $sort = $this->makeOrder($sort);
            $limit = $this->makeLimit($limit);
            $need = $this->makeNeed($need);
            $this->sql = "SELECT $need FROM `$tableName` {$str} $group {$sort} $limit";
            $_stmt = $this->getCon()->prepare($this->sql);
            if(!$_stmt){
                throw new ErrorException($this->sql);
            }
            if (is_array($where)) {
                $this->bindParams($_stmt, $where);
                unset($where);
            }
            $_stmt->execute();
            $res = $_stmt->fetchAll($flag);
            $_stmt->closeCursor();
            if (!is_array($res) || !$res) {
                return [];
            } else {
                return $res;
            }
        } catch (PDOException  $e) {
            $this->handle_db_error();
            throw new ErrorException($e->getMessage());
        } catch (ErrorException $e) {
            throw new ErrorException($e->getMessage());
        }
    }

    /**
     * @param string $data
     * @return bool
     * @throws ErrorException
     */
    public function addObj($data = '')
    {
        $tableName = $this->getTableName();
        try {
            if (!$tableName)
                throw new ErrorException('no table');


            $arr = $this->makeSetData($data);
            $insert = $arr[0];
            $data = $arr[1];

            $this->sql = "INSERT INTO `$tableName` $insert";

            $_stmt = $this->getCon()->prepare($this->sql);
            if(!$_stmt){
                throw new ErrorException($this->getCon()->errorInfo());
            }
            $this->bindParams($_stmt, $data);
            $ret = $_stmt->execute();
            $_stmt->closeCursor();
            return $ret;
        } catch (PDOException  $e) {
            $this->handle_db_error();
            throw new ErrorException($e->getMessage());
        } catch (ErrorException $e) {
            throw new ErrorException($e->getMessage());
        }
    }

    /**
     * @param string $data
     * @return bool
     * @throws ErrorException
     */
    public function replaceObj($data = '')
    {
        $tableName = $this->getTableName();
        try {
            if (!$tableName)
                throw new ErrorException('no table');

            $arr = $this->makeSetData($data);
            $insert = $arr[0];
            $data = $arr[1];

            $this->sql = "REPLACE INTO `$tableName` $insert";

            $_stmt = $this->getCon()->prepare($this->sql);
            if(!$_stmt){
                throw new ErrorException($this->sql);
            }
            $this->bindParams($_stmt, $data);
            $ret = $_stmt->execute();
            $_stmt->closeCursor();
            return $ret;
        } catch (PDOException  $e) {
            $this->handle_db_error();
            throw new ErrorException($e->getMessage());
        } catch (ErrorException $e) {
            throw new ErrorException($e->getMessage());
        }
    }

    /**
     * @param string $where
     * @return bool
     * @throws ErrorException
     */
    public function removeObj($where = '')
    {
        $tableName = $this->getTableName();
        try {
            if (!$tableName)
                throw new ErrorException('no table');

            $str = $this->makeWhere($where);
            if (!$str)
                throw new ErrorException('cant remove');

            $this->sql = "DELETE FROM `$tableName` {$str}";
            $_stmt = $this->getCon()->prepare($this->sql);
            if(!$_stmt){
                throw new ErrorException($this->sql);
            }
            $this->bindParams($_stmt, [$where]);

            $ret = $_stmt->execute();
            $_stmt->closeCursor();
            return $ret;
        } catch (PDOException  $e) {
            $this->handle_db_error();
            throw new ErrorException($e->getMessage());
        } catch (ErrorException $e) {
            throw new ErrorException($e->getMessage());
        }
    }

    /**
     * @param array $data
     * @param array $where
     * @return bool
     * @throws ErrorException
     */
    public function updateObj($data = [], $where = [])
    {
        $tableName = $this->getTableName();
        try {
            if (!$tableName)
                throw new ErrorException('no table');

            if (!$data)
                throw new ErrorException('no need to update');

            $str = $this->makeWhere($where);
            if (!$str)
                throw new ErrorException('cant update');

            $arr = $this->makeSetData($data);
            $set = $arr[0];
            $data = $arr[1];

            $this->sql = "UPDATE $tableName $set $str";
            $_stmt = $this->getCon()->prepare($this->sql);
            if(!$_stmt){
                throw new ErrorException(
                    $this->getCon()->errorCode() . $this->getCon()->errorInfo());
            }
            $this->bindParams($_stmt, [$data, $where]);
            $ret = $_stmt->execute();
            $_stmt->closeCursor();
            return $ret;
        } catch (PDOException  $e) {
            $this->handle_db_error();
            throw new ErrorException($e->getMessage());
        } catch (ErrorException $e) {
            throw new ErrorException($e->getMessage());
        }
    }

    /**
     * @param $sql
     * @return mixed
     * @throws ErrorException
     */
    public function getObjBySql($sql)
    {
        try {
            $this->sql = $sql;
            $_stmt = $this->getCon()->prepare($this->sql);
            if(!$_stmt){
                throw new ErrorException($this->sql);
            }
            $_stmt->execute();
            $res = $_stmt->fetch(PDO::FETCH_ASSOC);
            $_stmt->closeCursor();
            return $res;
        } catch (ErrorException $e) {
            throw new ErrorException($e->getMessage());
        }
    }

    /**
     * @param $sql
     * @return array
     * @throws ErrorException
     */
    public function getObjArrBySql($sql)
    {
        try {
            $this->sql = $sql;
            $_stmt = $this->getCon()->prepare($this->sql);
            if(!$_stmt){
                throw new ErrorException($this->sql);
            }
            $_stmt->execute();
            $res = $_stmt->fetchAll(PDO::FETCH_ASSOC);
            $_stmt->closeCursor();
            return $res;
        } catch (ErrorException $e) {
            throw new ErrorException($e->getMessage());
        }
    }

    /**
     * @param $arr
     * @return bool
     * @internal param $call_back
     */
    public function transaction($arr): bool
    {
        $this->getCon()->beginTransaction();
        try{
            foreach ($arr as $res){
                if (gettype($res) !== 'object')
                    throw new ErrorException();
                $res = (bool)$res();
                if (!$res){
                    throw new ErrorException();
                }
            }
            $this->getCon()->commit();
            return true;
        }catch (PDOException $e){
            $this->handle_db_error();
            $this->getCon()->rollBack();
        }catch (ErrorException $e){
            $this->getCon()->rollBack();
        }
        return false;
    }

    /**
     * @param $key
     * @return PDO
     * @throws ErrorException
     */
    function buildConnection($key)
    {
        $db = new CMysql();
        if (false === $db->init(Db::getInstance()->getHost($key), Db::getInstance()->getUser($key), Db::getInstance()->getPwd($key), $key)) {
            throw new ErrorException("connect db $key error:" . $db->errmsg());
        }
        return $db->m_pdo;
        /**
        $mysql_config = Config::getConf('tao', 'mysql');
        $pdo = new PDO('mysql:host=' . $mysql_config['host'] . ';dbname=tao', $mysql_config['user'], $mysql_config['pwd'], [
        PDO::MYSQL_ATTR_INIT_COMMAND => "set names utf8mb4",
        PDO::ATTR_STRINGIFY_FETCHES => false,
        PDO::ATTR_PERSISTENT => true
        ]);
        return $pdo;
         */
    }
}